#!/usr/bin/env ruby

########################################

class Extent
  include Comparable
  
  # Initializer
  
  def initialize(numeric)
    unless numeric.is_a?(Numeric)
      raise TypeError, 
        "#{self.class}.new must take Numeric (#{numeric.class})", caller
    end
    @mag = numeric
  end
  
  attr_reader :mag
  
  # Comparison
  
  def <=>(extent_or_numeric)
    other = extent_or_numeric
    return @mag <=> other.mag if other.is_a?(Extent)
    return @mag <=> other if other.is_a?(Numeric)
    raise TypeError, 
      "can't compare #{self.class} with #{other.class}", caller
  end
  
  # Arithmetic
  
  def +(extent)
    return self.class.new(@mag + extent.mag) if extent.is_a?(Extent)
    raise TypeError, 
      "can't add #{extent.class} to #{self.class}", caller
  end
  
  def -(extent)
    return self.class.new(@mag - extent.mag) if extent.is_a?(Extent)
    raise TypeError, 
      "can't subtract #{extent.class} from #{self.class}", caller
  end
  
  def *(numeric)
    return self.class.new(@mag * numeric) if numeric.is_a?(Numeric)
    raise TypeError, 
      "can't multiply #{self.class} by #{numeric.class}", caller
  end
  
  def /(extent_or_numeric)
    other = extent_or_numeric
    return (@mag / other.mag) if other.is_a?(Extent)
    return self.class.new(@mag / other) if other.is_a?(Numeric)
    raise TypeError, 
      "can't divide #{self.class} by #{other.class}", caller
  end
  
  # Coercion needs inner class
  
  class SpecialNumber
    def initialize(numeric)
      @val = numeric
    end
    
    include Comparable
    def <=>(extent)
      @val <=> extent.mag
    end
    
    def *(extent)
      extent.class.new(@val * extent.mag)
    end
    
    def method_missing(op, extent, *args)
      raise TypeError, 
        "#{@val.class} #{op} #{extent.class} not allowed", caller
    end
  end
  
  def coerce(other)
    return [SpecialNumber.new(other), self] if other.is_a?(Numeric)
    raise TypeError, 
      "#{self.class} can't be coerced into #{other.class}", caller
  end
  
  # Number outputs
  
  def to_f
    @mag.to_f
  end
  
  def to_i
    @mag.to_i
  end
  
  # String output
  
  def to_s
    @mag.to_s
  end
end

########################################

if $0 == __FILE__
  p Extent.instance_methods.sort
  p Extent::SpecialNumber.instance_methods.sort
  
  class DerivedExtent < Extent; end
  
  e0   = DerivedExtent.new(  0)
  e2   = DerivedExtent.new(  2)
  e3   = DerivedExtent.new(  3)
  e5   = DerivedExtent.new(  5)
  e123 = DerivedExtent.new(123)
  #puts e123.mag
  
  p e123 + e0
  p e123 + e5
  #fail "don't coerce for +" if (5 + e123) == 128
  
  p e123 - e0
  p e123 - e5
  #fail "don't coerce for -" if (128 - e123) == 5
  
  p e123 * 0
  p e123 * 2
  p 2 * e123
  
  third = 1.0 / 3.0
  p e123 * third
  p third * e123
  
  p e123 / e3
  p e123 / 3
  #fail "don't coerce for /" if (123 / e3).between?(40, 42)
  
  #fail "dont' coerce for other ops" if (2 % e5) == 2
end

########################################

=begin
#doctest Demo Extent.<=>
>> Extent.new(2) < Extent.new(5)
=> true
>> Extent.new(2) < 5
=> true
>> 2 < Extent.new(5)
=> true
=end

=begin
#doctest Demo Extent.+
>> Extent.new(2) + Extent.new(5)
=> #<Extent:0x15cc60 @mag=7>
>> Extent.new(2) + 5
TypeError: can't add Fixnum to Extent
>> 2 + Extent.new(5)
TypeError: Fixnum + Extent not allowed
=end

=begin
#doctest Demo Extent.-
>> Extent.new(2) - Extent.new(5)
=> #<Extent:0x156658 @mag=-3>
>> Extent.new(2) - 5
TypeError: can't subtract Fixnum from Extent
>> 2 - Extent.new(5)
TypeError: Fixnum - Extent not allowed
=end

=begin
#doctest Demo Extent.*
>> Extent.new(2) * Extent.new(5)
TypeError: can't multiply Extent by Extent
>> Extent.new(2) * 5
=> #<Extent:0x13060c @mag=10>
>> 2 * Extent.new(5)
=> #<Extent:0x12ea3c @mag=10>
=end

=begin
#doctest Demo Extent./
>> Extent.new(5.0) / Extent.new(2.0)
=> 2.5
>> Extent.new(5.0) / 2.0
=> #<Extent:0x129b18 @mag=2.5>
>> 5.0 / Extent.new(2.0)
TypeError: Float / Extent not allowed
=end
